//-
// ==========================================================================
// Copyright (C) 1995 - 2006 Autodesk, Inc. and/or its licensors.  All 
// rights reserved.
//
// The coded instructions, statements, computer programs, and/or related 
// material (collectively the "Data") in these files contain unpublished 
// information proprietary to Autodesk, Inc. ("Autodesk") and/or its 
// licensors, which is protected by U.S. and Canadian federal copyright 
// law and by international treaties.
//
// The Data is provided for use exclusively by You. You have the right 
// to use, modify, and incorporate this Data into other products for 
// purposes authorized by the Autodesk software license agreement, 
// without fee.
//
// The copyright notices in the Software and this entire statement, 
// including the above license grant, this restriction and the 
// following disclaimer, must be included in all copies of the 
// Software, in whole or in part, and all derivative works of 
// the Software, unless such copies or derivative works are solely 
// in the form of machine-executable object code generated by a 
// source language processor.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. 
// AUTODESK DOES NOT MAKE AND HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED 
// WARRANTIES INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF 
// NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 
// PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE, OR 
// TRADE PRACTICE. IN NO EVENT WILL AUTODESK AND/OR ITS LICENSORS 
// BE LIABLE FOR ANY LOST REVENUES, DATA, OR PROFITS, OR SPECIAL, 
// DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES, EVEN IF AUTODESK 
// AND/OR ITS LICENSORS HAS BEEN ADVISED OF THE POSSIBILITY 
// OR PROBABILITY OF SUCH DAMAGES.
//
// ==========================================================================
//+

#ifdef WIN32
#pragma warning( disable : 4786 )
#endif

#include <maya/MIOStream.h>
#include <maya/MDagPath.h>
#include <maya/MDoubleArray.h>
#include <maya/MFloatVector.h>
#include <maya/MFnDependencyNode.h>
#include <maya/MFnLightDataAttribute.h>
#include <maya/MFnMesh.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MGlobal.h>
#include <maya/MPlug.h>
#include <maya/MPlugArray.h>
#include <maya/MString.h>

#if defined(OSMac_MachO_)
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#else
#include <GL/gl.h>
#include <GL/glu.h>
#endif

#include "blindDataShader.h"

void mergeSort( int startIndex, int sortingOffset,
			    MIntArray& sortingArray, int* tempSortingArray,
				MDoubleArray& otherArray, double* tempOtherArray)
//
// Description:
// This is a very dirty recursive implementation of the merge sort
// that is very application-specific
//
{
	if (sortingOffset == 2)
	{
		if (sortingArray[startIndex] > sortingArray[startIndex+1])
		{
			int temp = sortingArray[startIndex+1];
			sortingArray[startIndex+1] = sortingArray[startIndex];
			sortingArray[startIndex] = temp;
			double ftemp = otherArray[startIndex+1];
			otherArray[startIndex+1] = otherArray[startIndex];
			otherArray[startIndex] = ftemp;
		}
	}
	else if (sortingOffset > 2)
	{
		// First, sort each half
		//
		int leftCount = sortingOffset / 2;
		int rightCount = sortingOffset - leftCount;
		int leftIndex = startIndex;
		int rightIndex = startIndex + leftCount;
		mergeSort(leftIndex, leftCount, sortingArray, tempSortingArray,
			otherArray, tempOtherArray);
		mergeSort(rightIndex, rightCount, sortingArray, tempSortingArray,
			otherArray, tempOtherArray);

		// Second, merge the halves into the temporary arrays
		//
		int index = startIndex;
		while (leftIndex < (startIndex + leftCount)
			&& rightIndex < (startIndex + sortingOffset) )
		{
			if (sortingArray[leftIndex] < sortingArray[rightIndex])
			{
				tempSortingArray[index] = sortingArray[leftIndex];
				tempOtherArray[index++] = otherArray[leftIndex++];
			}
			else
			{
				tempSortingArray[index] = sortingArray[rightIndex];
				tempOtherArray[index++] = otherArray[rightIndex++];
			}
		}

		// One of the sides is done, so add the other side
		//
		while (leftIndex < (startIndex + leftCount) )
		{
			tempSortingArray[index] = sortingArray[leftIndex];
			tempOtherArray[index++] = otherArray[leftIndex++];
		}
		while (rightIndex < (startIndex + sortingOffset) )
		{
			tempSortingArray[index] = sortingArray[rightIndex];
			tempOtherArray[index++] = otherArray[rightIndex++];
		}

		// Finally, copy the temporary sorting array over to the other side
		//
		for (index = startIndex; index < (startIndex + sortingOffset); ++index)
		{
			sortingArray[index] = tempSortingArray[index];
			otherArray[index] = tempOtherArray[index];
		}
	}
}

int binarySearch(int searchValue, MIntArray& searchArray)
//
// Description:
// Application-specific implementation of the binary search algorithm.
// It returns the index in the "searchArray" where the "searchValue"
// is found or -1 if it is not found.
//
{
	int max = searchArray.length();
	int min = 0;
	while (max - min > 1)
	{
		int currentIndex = ((max - min) / 2) + min;
		int currentValue = searchArray[currentIndex];
		if (currentValue == searchValue) return currentIndex;
		else if (currentValue < searchValue) min = currentIndex;
		else max = currentIndex;
	}
	if (searchArray[min] == searchValue) return min;
	else return -1;
}

/****************************************************************************
 * Blind Data Shader class
 ***************************************************************************/

// Static class member variables
//
// Unique Node TypeId
MTypeId blindDataShader::id( 0x00086000 );

void* blindDataShader::creator()
//
// Description: Static member function that returns a new
// dynamically-allocated instance of the class.
//
{
    return new blindDataShader();
}

MStatus blindDataShader::initialize()
//
// Description: Static member function that initializes the static
// member variables of the class. In this case, it does nothing, since
// "id" is already set to the default value.
//
{
	return MS::kSuccess;
}

MStatus blindDataShader::compute(
const MPlug&      plug,
      MDataBlock& block ) 
{ 
    bool k = false;
    k |= (plug==outColor);
    k |= (plug==outColorR);
    k |= (plug==outColorG);
    k |= (plug==outColorB);
    if( !k ) return MS::kUnknownParameter;

	// Always return black for now.
    MFloatVector resultColor(0.0,0.0,0.0);

    // set ouput color attribute
    MDataHandle outColorHandle = block.outputValue( outColor );
    MFloatVector& outColor = outColorHandle.asFloatVector();
    outColor = resultColor;
    outColorHandle.setClean();

    return MS::kSuccess;
}

MStatus	blindDataShader::bind(const MDrawRequest& request, M3dView& view)
{
	view.beginGL();

	glPushAttrib( GL_ALL_ATTRIB_BITS );
	glPushClientAttrib(GL_CLIENT_VERTEX_ARRAY_BIT);

	view.endGL();

	return MS::kSuccess;
}


MStatus	blindDataShader::unbind(const MDrawRequest& request,
			   M3dView& view)
{
	view.beginGL();	

	glPopClientAttrib();
	glPopAttrib();

	view.endGL();

	return MS::kSuccess;
}

MStatus	blindDataShader::geometry( const MDrawRequest& request,
								M3dView& view,
							    int prim,
								unsigned int writable,
								int indexCount,
								const unsigned int * indexArray,
								int vertexCount,
							    const int * vertexIDs,
								const float * vertexArray,
								int normalCount,
								const float ** normalArrays,
								int colorCount,
								const float ** colorArrays,
								int texCoordCount,
								const float ** texCoordArrays)
//
// Description:
// Once everything is plugged, this function is called by the Maya rendering
// engine to render the given objects. It will be called for each frame, so 
// the efficiency of this algorithm is important.
//
{
	const int blindDataUniqueID = 60;
	MStatus stat;
	int i;

	// Create our own empty colour array and
	// populate it with the default colour value for the attribute.
	//
	double* colours = new double[vertexCount * 3];
	float defaultColour[3] = { 0.0f, 0.0f, 0.0f };
	MPlug plug( thisMObject(), outColor );
	MObject colorObject;
	plug.getValue(colorObject);
	MFnNumericData data(colorObject);
	data.getData(defaultColour[0], defaultColour[1], defaultColour[2]);

	// Use the blind data to set independant colour values
	//
	MFnMesh mesh(request.multiPath().node(), &stat);
	if (stat && mesh.hasBlindData(MFn::kMeshVertComponent, &stat)
		&& mesh.hasBlindData(vertexIDs[0], MFn::kMeshVertComponent,
		blindDataUniqueID, &stat))
	{
		MIntArray redComponentIDs, greenComponentIDs, blueComponentIDs;
		MDoubleArray redColour, greenColour, blueColour;

		stat = mesh.getDoubleBlindData( MFn::kMeshVertComponent,
			blindDataUniqueID, "red", redComponentIDs, redColour);
		if (stat) stat = mesh.getDoubleBlindData( MFn::kMeshVertComponent,
			blindDataUniqueID, "green", greenComponentIDs, greenColour);
		if (stat) stat = mesh.getDoubleBlindData( MFn::kMeshVertComponent,
			blindDataUniqueID, "blue", blueComponentIDs, blueColour);

		if (stat && redComponentIDs.length() == redColour.length()
			&& greenComponentIDs.length() == greenColour.length()
			&& blueComponentIDs.length() == blueColour.length())
		{
			// Now that I have all the blind data,
			// use the vertex IDs and the component IDs arrays
			// to map the blind data values inside the colour array.
			//

			// Sort the components IDs and the colour values in increasing
			// order to speed up the association
			//
			int maxArrayLength =
				(redComponentIDs.length() > greenComponentIDs.length()) ?
				((redComponentIDs.length() > blueComponentIDs.length()) ?
					redComponentIDs.length() : blueComponentIDs.length()) :
				((greenComponentIDs.length() > blueComponentIDs.length()) ?
					greenComponentIDs.length() : blueComponentIDs.length());
			int* tempIntArray = new int[maxArrayLength];
			double* tempFloatArray = new double[maxArrayLength];
			mergeSort(0, redComponentIDs.length(), redComponentIDs,
				tempIntArray, redColour, tempFloatArray);
			mergeSort(0, greenComponentIDs.length(), greenComponentIDs,
				tempIntArray, greenColour, tempFloatArray);
			mergeSort(0, blueComponentIDs.length(), blueComponentIDs,
				tempIntArray, blueColour, tempFloatArray);

			// Associate the vertex IDs with the colour values
			//
			for (i = 0; i < vertexCount; ++i)
			{
				int index = binarySearch(vertexIDs[i], redComponentIDs);
				if (index != -1) colours[3*i] = redColour[index];
				else colours[3*i] = defaultColour[0];

				index = binarySearch(vertexIDs[i], greenComponentIDs);
				if (index != -1) colours[3*i+1] = greenColour[index];
				else colours[3*i+1] = defaultColour[1];

				index = binarySearch(vertexIDs[i], blueComponentIDs);
				if (index != -1) colours[3*i+2] = blueColour[index];
				else colours[3*i+2] = defaultColour[2];
			}
		}
	}

	// Render the triangles using the new vertex colour information
	// taken from the blind data values of the mesh
	//
	view.beginGL();

	glPushAttrib( GL_ALL_ATTRIB_BITS );
	glPushClientAttrib(GL_CLIENT_VERTEX_ARRAY_BIT);

	glDisable(GL_LIGHTING);

	glVertexPointer(3, GL_FLOAT, 0, vertexArray);
	glEnableClientState(GL_VERTEX_ARRAY);

	// Is the next line necessary?
	glColor4f( 1.0f, 1.0f, 1.0f, 1.0f );

	glColorPointer(3, GL_DOUBLE, 0, colours);
	glEnableClientState(GL_COLOR_ARRAY);

	glDrawElements(prim, indexCount, GL_UNSIGNED_INT, indexArray);

	glDisable(GL_COLOR_MATERIAL);

	glPopClientAttrib();
	glPopAttrib();

	view.endGL();

	return MS::kSuccess;
}
